package com.soft.collateral.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.soft.collateral.constant.RedisConstant;
import com.soft.collateral.dto.MenuDTO;
import com.soft.collateral.entity.Menu;
import com.soft.collateral.entity.RoleMenu;
import com.soft.collateral.mapper.MenuMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.soft.collateral.mapper.MetaMapper;
import com.soft.collateral.mapper.RoleMenuMapper;
import com.soft.collateral.service.MenuService;
import com.soft.collateral.service.MetaService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@CacheConfig(cacheNames = RedisConstant.MENU_KEY)
@Slf4j
@Service
@RequiredArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {

    private final MenuMapper menuMapper;

    private final RedisTemplate<String, Object> redisTemplate;

    private final MetaMapper metaMapper;

    private final MetaService metaService;

    private final RoleMenuMapper roleMenuMapper;

    /**
     * 根据当前登录者的ID，查询其拥有的菜单
     * @param userId
     * @return
     */
    @Cacheable(key = "'user_' + #userId")
    @Transactional(readOnly = true)
    @Override
    public List<MenuDTO> findAll(Long userId) {
        List<MenuDTO> list = new ArrayList<>();

        //查询一级菜单
        List<Menu> firstMenus = menuMapper.selectFirstMenuByUid(userId);
        firstMenus.forEach(menu ->{
            //组装一级菜单
            MenuDTO first = new MenuDTO();
            BeanUtils.copyProperties(menu , first);

            //查询二级菜单
            Integer id = menu.getId();
            List<MenuDTO> children = menuMapper.selectSecondMenuByParentId(id);
            //组装二级菜单
            first.setChildren(children);
            list.add(first);
        });
        return list;
    }

    /**
     * 获取所有菜单
     * @return
     */
    @Cacheable(key = "'" + RedisConstant.ALL_MENUS_KEY + "'")
    @Override
    public List<Menu> getAllMenus() {
        List<Menu> menus = this.list();
        for (Menu menu : menus) {
            menu.setMeta(metaMapper.selectById(menu.getMetaId()));
        }
        return menus;
    }


    /**
     * 根据id获取菜单
     * @param id
     * @return
     */
    @Cacheable(key = "'" + RedisConstant.MENU_ID_KEY + "' + #id")
    @Override
    public Menu getMenuById(Integer id) {
        Menu menu = this.getById(id);
        if (menu != null) {
            menu.setMeta(metaMapper.selectById(menu.getMetaId()));
        }
        return menu;
    }

    /**
     * 新增菜单
     * @param menu
     * @return
     */
    @CacheEvict(allEntries = true)
    @Override
    @Transactional
    public boolean addMenu(Menu menu) {
        // 1. 先删除缓存
        redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.ALL_MENUS_KEY);

        // 2. 先插入 meta 表
        if (menu.getMeta() != null) {
            metaMapper.insert(menu.getMeta());
            // 3. 获取自增主键
            menu.setMetaId(menu.getMeta().getId());
        }

        // 4. 再插入 menu 表
        boolean result = this.save(menu);

        // 5. 延迟双删策略，再次删除缓存
        try {
            TimeUnit.MILLISECONDS.sleep(300);
            redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.ALL_MENUS_KEY);
        } catch (InterruptedException e) {
            log.error("延迟双删失败", e);
            Thread.currentThread().interrupt();
        }

        return result;
    }

    /**
     * 更新菜单
     * @param menu
     * @return
     */
    @CacheEvict(allEntries = true)
    @Override
    public boolean updateMenu(Menu menu) {
        // 1. 先删除缓存
        redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.ALL_MENUS_KEY);
        redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.MENU_ID_KEY + menu.getId());

        // 2. 先更新 menu 表
        boolean menuUpdated = this.updateById(menu);

        // 3. 再更新 meta 表
        if (menu.getMeta() != null && menu.getMeta().getId() != null) {
            metaMapper.updateById(menu.getMeta());
        }

        // 4. 延迟双删策略，再次删除缓存
        try {
            TimeUnit.MILLISECONDS.sleep(300);
            redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.ALL_MENUS_KEY);
            redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.MENU_ID_KEY + menu.getId());
        } catch (InterruptedException e) {
            log.error("延迟双删失败", e);
            Thread.currentThread().interrupt();
        }

        return menuUpdated;
    }

    /**
     * 删除单个菜单
     * @param id
     * @return
     */
    @CacheEvict(allEntries = true)
    @Override
    public boolean deleteMenu(Integer id) {
        // 1. 先删除缓存
        redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.ALL_MENUS_KEY);
        redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.MENU_ID_KEY + id);

        // 2. 删除数据
        boolean result = this.removeById(id);

        // 3. 延迟双删策略，再次删除缓存
        try {
            TimeUnit.MILLISECONDS.sleep(300);
            redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.ALL_MENUS_KEY);
            redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.MENU_ID_KEY + id);
        } catch (InterruptedException e) {
            log.error("延迟双删失败", e);
            Thread.currentThread().interrupt();
        }

        return result;
    }

    /**
     * 删除菜单及其关联的meta
     * @param id 菜单ID
     * @return
     */
    @CacheEvict(allEntries = true)
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteMenuCascade(Integer id) {
        // 1. 先删除缓存
        redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.ALL_MENUS_KEY);
        redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.MENU_ID_KEY + id);

        // 2. 先查询菜单信息，获取metaId
        Menu menu = this.getById(id);
        if (menu == null) {
            return false;
        }

        // 3. 删除菜单
        boolean menuDeleted = this.removeById(id);
        if (!menuDeleted) {
            return false;
        }

        // 4. 如果存在关联的meta数据，则删除meta数据
        if (menu.getMetaId() != null) {
            metaService.removeById(menu.getMetaId());
        }

        // 5. 延迟双删策略，再次删除缓存
        try {
            TimeUnit.MILLISECONDS.sleep(300);
            redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.ALL_MENUS_KEY);
            redisTemplate.delete(RedisConstant.MENU_KEY + "::" + RedisConstant.MENU_ID_KEY + id);
        } catch (InterruptedException e) {
            log.error("延迟双删失败", e);
            Thread.currentThread().interrupt();
        }

        return true;
    }

    /**
     * 为菜单分配角色
     */
    @Override
    @Transactional
    @CacheEvict(allEntries = true)
    public boolean assignRolesToMenu(Integer menuId, Integer[] roleIds) {
        // 先删除原有关系
        LambdaQueryWrapper<RoleMenu> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RoleMenu::getMenuId, menuId);
        roleMenuMapper.delete(wrapper);

        // 添加新关系
        if (roleIds != null && roleIds.length > 0) {
            for (Integer roleId : roleIds) {
                RoleMenu roleMenu = new RoleMenu();
                roleMenu.setMenuId(menuId);
                roleMenu.setRoleId(roleId);
                roleMenuMapper.insert(roleMenu);
            }
        }
        return true;
    }

    /**
     * 根据菜单ID获取角色ID列表
     */
    @Override
    @Transactional(readOnly = true)
    public List<Integer> getRoleIdsByMenuId(Integer menuId) {
        LambdaQueryWrapper<RoleMenu> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RoleMenu::getMenuId, menuId);
        return roleMenuMapper.selectList(wrapper).stream()
                .map(RoleMenu::getRoleId)
                .collect(Collectors.toList());
    }
}
